module nade.obsp;

import derelict.opengl.gl;

import nade.coordinate;
import nade.color;
import nade.primitive;


class OBSP {
	enum Axis {
		X,
		Y,
		Z,
	}


	this(Material* material = null, const Region3 bbox = Region3(int.min, int.min, int.min, int.max, int.max, int.max))
	{
		_root = new Leaf(bbox, material);
	}

	void render()
	{
		glEnable(GL_TEXTURE_2D);
		_root.render();
		glDisable(GL_TEXTURE_2D);
	}

	void cut(const Region3 box, Material* material)
	{
		_root = _root.cut(box, material);
	}

	private {
		Node			_root;
	}


	class Node {
		this(const Region3 bbox)
		{
			_bbox = bbox;
		}

		abstract void render();
		abstract Node cut(const Region3 box, Material* material);

		Region3 bbox() { return _bbox; }

		private {
			Region3			_bbox;
		}
	}


	class Cutter(Axis A) : Node {
		this(const Region3 bbox, int offset, Node back, Node front)
		{
			super(bbox);

			_offset = offset;
			_back = back;
			_front = front;
		}

		override void render()
		{
			_back.render();
			_front.render();
		}

		override Node cut(const Region3 box, Material* material)
		{
			static if(A == Axis.X)
			{
				if(_offset > box.from.x  &&  _offset < box.to.x)
				{
					_back = _back.cut(box.cutBackX(_offset), material);
					_front = _front.cut(box.cutFrontX(_offset), material);
				}
				else if(_offset <= box.from.x)
				{
					_front = _front.cut(box, material);
				}
				else if(_offset >= box.to.x)
				{
					_back = _back.cut(box, material);
				}
			}
			else static if(A == Axis.Y)
			{
				if(_offset > box.from.y  &&  _offset < box.to.y)
				{
					_back = _back.cut(box.cutBackY(_offset), material);
					_front = _front.cut(box.cutFrontY(_offset), material);
				}
				else if(_offset <= box.from.y)
				{
					_front = _front.cut(box, material);
				}
				else if(_offset >= box.to.y)
				{
					_back = _back.cut(box, material);
				}
			}
			else static if(A == Axis.Z)
			{
				if(_offset > box.from.z  &&  _offset < box.to.z)
				{
					_back = _back.cut(box.cutBackZ(_offset), material);
					_front = _front.cut(box.cutFrontZ(_offset), material);
				}
				else if(_offset <= box.from.z)
				{
					_front = _front.cut(box, material);
				}
				else if(_offset >= box.to.z)
				{
					_back = _back.cut(box, material);
				}
			}

			return this;
		}

		private {
			int			_offset;
			Node		_back;
			Node		_front;
		}
	}


	class Leaf : Node {
		this(const Region3 bbox, Material* material)
		{
			super(bbox);

			_material = material;
		}

		override void render()
		{
			if(_material is null) return;

			glBindTexture(GL_TEXTURE_2D, _material.texture);

			renderSolidBox(_bbox);
		}

		override Node cut(const Region3 box, Material* material)
		{
			if(box == _bbox)
			{
				_material = material;
			}
			else if(box.from.x > _bbox.from.x)
			{
				auto cutter = new Cutter!(Axis.X)(
					_bbox,
					box.from.x,
					this,
					new Leaf(_bbox.cutFrontX(box.from.x), _material)
					);

				_bbox.to.x = box.from.x;

				return cutter.cut(box, material);
			}
			else if(box.to.x < _bbox.to.x)
			{
				auto cutter = new Cutter!(Axis.X)(
					_bbox,
					box.to.x,
					new Leaf(_bbox.cutBackX(box.to.x), _material),
					this
					);

				_bbox.from.x = box.to.x;

				return cutter.cut(box, material);
			}
			else if(box.from.y > _bbox.from.y)
			{
				auto cutter = new Cutter!(Axis.Y)(
					_bbox,
					box.from.y,
					this,
					new Leaf(_bbox.cutFrontY(box.from.y), _material)
					);

				_bbox.to.y = box.from.y;

				return cutter.cut(box, material);
			}
			else if(box.to.y < _bbox.to.y)
			{
				auto cutter = new Cutter!(Axis.Y)(
					_bbox,
					box.to.y,
					new Leaf(_bbox.cutBackY(box.to.y), _material),
					this
					);

				_bbox.from.y = box.to.y;

				return cutter.cut(box, material);
			}
			else if(box.from.z > _bbox.from.z)
			{
				auto cutter = new Cutter!(Axis.Z)(
					_bbox,
					box.from.z,
					this,
					new Leaf(_bbox.cutFrontZ(box.from.z), _material)
					);

				_bbox.to.z = box.from.z;

				return cutter.cut(box, material);
			}
			else if(box.to.z < _bbox.to.z)
			{
				auto cutter = new Cutter!(Axis.Z)(
					_bbox,
					box.to.z,
					new Leaf(_bbox.cutBackZ(box.to.z), _material),
					this
					);

				_bbox.from.z = box.to.z;

				return cutter.cut(box, material);
			}

			return this;
		}

		private {
			Material*		_material = null;
		}
	}
}
